package jumpingnotes;

import java.io.IOException;
import java.io.InputStream;
import java.io.ByteArrayInputStream;

import javax.servlet.ServletConfig;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import jumpingnotes.storage.StorageService;
import jumpingnotes.storage.UnsupportedStorageObject;
import jumpingnotes.model.*;
import jumpingnotes.dao.*;

import org.apache.commons.fileupload.FileItemIterator;
import org.apache.commons.fileupload.FileItemStream;
import org.apache.commons.fileupload.FileUploadException;
import org.apache.commons.fileupload.servlet.ServletFileUpload;
import org.springframework.web.context.WebApplicationContext;
import org.springframework.web.context.support.WebApplicationContextUtils;

public class Servlet extends HttpServlet
implements ResourceTypes {
	protected WebApplicationContext appCtx;
	protected StorageService storageService;
	protected BookDAO bookDAO;
	protected AudioDAO audioDAO;
	protected ResourceDAO resourceDAO;

	@Override
	public void init() throws ServletException {
		super.init();
		appCtx = WebApplicationContextUtils
			.getWebApplicationContext(getServletContext());
		if (appCtx == null) {
			throw new ServletException("Spring context not found!");
		}
		storageService = (StorageService) appCtx.getBean("storageService");
		bookDAO = (BookDAO) appCtx.getBean("bookDAO");
		audioDAO = (AudioDAO) appCtx.getBean("audioDAO");
		resourceDAO = (ResourceDAO) appCtx.getBean("resourceDAO");		
	}

	@Override
	protected void doGet(HttpServletRequest req, HttpServletResponse resp)
			throws ServletException, IOException {
		handleGetAndPost(req, resp, false);
	}

	@Override
	protected void doPost(HttpServletRequest req, HttpServletResponse resp)
			throws ServletException, IOException {
		handleGetAndPost(req, resp, true);		
	}

	protected void handleGetAndPost(
			HttpServletRequest req,
			HttpServletResponse resp,
			boolean isPost) throws ServletException, IOException {
		String servletPath = req.getServletPath();
		if ("/upload".equals(servletPath)) {
			handleUpload(req, resp);
		} else if ("/download".equals(servletPath)) {
			handleDownload(req, resp);
		} else {
			resp.setStatus(HttpServletResponse.SC_BAD_REQUEST);
		}
	}
	
	/**
	 * Upload URL should contain:
	 * (1) upload item of content type
	 * (2) upload item type "type" (as integer item)
	 * (3) resource uuid named "uuid"
	 * (4) mandatory fields of a specific type
	 * @param req
	 * @param resp
	 * @throws ServletException
	 * @throws IOException
	 */
	protected void handleUpload(HttpServletRequest req,
			HttpServletResponse resp)
	throws ServletException, IOException {
		// check type
		String typeStr = req.getParameter("type");
		int type = 0;
		if (typeStr != null) {
			try {
				type = Integer.parseInt(typeStr);
			} catch (NumberFormatException e) {
				resp.setStatus(HttpServletResponse.SC_BAD_REQUEST);
			}
		} else {
			resp.setStatus(HttpServletResponse.SC_BAD_REQUEST);
		}
		if (resp.isCommitted()) {
			return;
		}
		// check uuid
		String uuid = req.getParameter("uuid");
		if (uuid == null) {
			resp.setStatus(HttpServletResponse.SC_BAD_REQUEST);
		}
		if (resp.isCommitted()) {
			return;
		}
		// check if upload sync
		String syncStr = req.getParameter("content");
		if (syncStr != null && type == TYPE_SYNC) {
			uploadSync(req, resp, null, uuid);
			resp.setStatus(HttpServletResponse.SC_OK);
			return;
		}
		// check if upload binary
		boolean isMultipart = ServletFileUpload.isMultipartContent(req);
		if (!isMultipart) {
			resp.setStatus(HttpServletResponse.SC_BAD_REQUEST);
			return;
		}
		// Create a new file upload handler and find file item
		// with the field of content type from item.isFormField()
		FileItemStream contentItem = null;
		try {
    		ServletFileUpload upload = new ServletFileUpload();
    		FileItemIterator iter = upload.getItemIterator(req);
    		while (iter.hasNext()) {
    		    FileItemStream item = iter.next();
    		    if (!item.isFormField()) {
    		    	contentItem = item;
    		    	break;
    		    }
    		}
		} catch (FileUploadException e) {
			// do nothing
		}
		if (contentItem == null) {
			resp.setStatus(HttpServletResponse.SC_BAD_REQUEST);
		}
		if (resp.isCommitted()) {
			return;
		}
		if (contentItem != null) {
    		switch (type) {
    			case TYPE_BOOK:
    				uploadBook(req, resp, contentItem, uuid);
    				break;
    			case TYPE_AUDIO:
    				uploadAudio(req, resp, contentItem, uuid);
    				break;
    			case TYPE_SYNC:
    				uploadSync(req, resp, contentItem, uuid);
    				break;
    			default:
    				uploadOtherResource(req, resp, contentItem, uuid);
    				break;
    		}
		}
		if (!resp.isCommitted()) {
			resp.flushBuffer();
		}
	}
	
	/**
	 * Download URL should contain:
	 * (1) Resource type named "type" (as integer)
	 * (2) Resource uuid named "uuid"
	 * @param req
	 * @param resp
	 * @throws ServletException
	 * @throws IOException
	 */
	protected void handleDownload(HttpServletRequest req,
			HttpServletResponse resp)
	throws ServletException, IOException {
		// check type
		String typeStr = req.getParameter("type");
		int type = 0;
		if (typeStr != null) {
			try {
				type = Integer.parseInt(typeStr);
			} catch (NumberFormatException e) {
				resp.setStatus(HttpServletResponse.SC_BAD_REQUEST);
			}
		} else {
			resp.setStatus(HttpServletResponse.SC_BAD_REQUEST);
		}
		if (resp.isCommitted()) {
			return;
		}
		// check uuid
		String uuid = req.getParameter("uuid");
		if (uuid == null) {
			resp.setStatus(HttpServletResponse.SC_BAD_REQUEST);
		}
		if (resp.isCommitted()) {
			return;
		}
		String subtypeStr = req.getParameter("subtype");
		int subtype = ResourceTypes.SUBTYPE_RESERVED;
		if (subtypeStr!=null) {
			subtype = Integer.parseInt(subtypeStr);
		}			
		try {
			resp.setContentLength((int) storageService.getObjectSize(type, subtype, uuid));
			boolean found = storageService.loadStream(type, subtype, uuid, resp.getOutputStream());
			if (!found) {
				resp.setStatus(HttpServletResponse.SC_NOT_FOUND);
			}
		} catch (UnsupportedStorageObject e) {
			resp.setStatus(HttpServletResponse.SC_NOT_IMPLEMENTED);
		}
		if (!resp.isCommitted()) {
			resp.flushBuffer();
		}
	}
	
	private void uploadBook(
			HttpServletRequest req,
			HttpServletResponse resp,
			FileItemStream contentItem,
			String uuid)
	throws ServletException, IOException {
		InputStream contentStream = contentItem.openStream();
		try {
			/*Book book = new Book();
			book.setResourceUuid(uuid);
			book.setResourceType(ResourceTypes.TYPE_BOOK);
			book.setResourceSubType(0);
			book.setResourceName(req.getParameter("bookName"));
			book.setAuthor(req.getParameter("bookAuthor"));
			bookDAO.save(book);*/
			
			storageService.saveStream(ResourceTypes.TYPE_BOOK, 0, uuid, contentStream);
		}
		catch (Exception e) {
			e.printStackTrace();
		} finally {
			contentStream.close();
		}
	}
	
	private void uploadAudio(
			HttpServletRequest req,
			HttpServletResponse resp,
			FileItemStream contentItem,
			String uuid)
	throws ServletException, IOException {
		// TODO
	}
	
	private void uploadSync(
			HttpServletRequest req,
			HttpServletResponse resp,
			FileItemStream contentItem,
			String uuid)
	throws ServletException, IOException {
		// TODO
		String syncStr = req.getParameter("content");
		ByteArrayInputStream contentStream = new ByteArrayInputStream(syncStr.getBytes());
		try {
			Resource sync = new Resource();
			sync.setResourceUuid(uuid);
			sync.setResourceName(uuid);
			sync.setResourceType(ResourceTypes.TYPE_SYNC);
			sync.setResourceSubType(ResourceTypes.SUBTYPE_RESERVED);
			int syncId = resourceDAO.save(sync);
			
			String audioUuid = req.getParameter("audioUuid");
			Resource savedSync = resourceDAO.findById(syncId);
			Audio audio = (Audio) resourceDAO.findByUuid(audioUuid);
			audio.setSync(savedSync);
			audioDAO.update(audio);	
			
			storageService.saveStream(ResourceTypes.TYPE_SYNC, 0, uuid, contentStream);
		} catch(Exception e) {
			e.printStackTrace();
		} finally {
			contentStream.close();
		}
	}
	
	private void uploadOtherResource(
			HttpServletRequest req,
			HttpServletResponse resp,
			FileItemStream contentItem,
			String uuid)
	throws ServletException, IOException {
		// TODO
	}
}
